/*
 * json_viewer.c
 * This file is part of gURL
 *
 * Copyright (C) 2014 - Aleksey Konovalov
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Library General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "json_viewer.h"

#ifdef HAVE_JSON_GLIB

#include "defines.h"
#include <glib/gi18n.h>
#include <json-glib/json-glib.h>
#include "utils.h"

G_DEFINE_TYPE(JsonViewer, json_viewer, GTK_TYPE_TREE_VIEW)

enum {
    COLUMN_NAME,
    COLUMN_VALUE,
    COLUMNS_COUNT
};

static void json_viewer_class_init(JsonViewerClass* UNUSED(klass)) {}

static void save_item(const char* data, size_t len) {
    show_save_dlg_and_save_data(data, len, _("Save data as..."), "data.txt");
}

static void on_save_item_clicked(GtkWidget* UNUSED(menuitem),
                                 const char* data) {
    save_item(data, -1);
}

static void on_b64decode_and_save_item_clicked(GtkWidget* UNUSED(menuitem),
                                               const char* b64data) {
    gsize out_len = 0;
    guchar* data = g_base64_decode(b64data, &out_len);
    save_item((const char*)data, out_len);
    g_free(data);
}

static void do_view_popup_menu(const char* node_value, GdkEventButton* event) {
    GtkWidget* menu = gtk_menu_new();
    GtkWidget* menuitem = gtk_menu_item_new_with_label(_("Save node value"));
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
    g_signal_connect(menuitem, "activate",
                     G_CALLBACK(on_save_item_clicked), (gpointer)node_value);

    if(is_base64_data(node_value)) {
        menuitem = gtk_menu_item_new_with_label(
                    _("Decode node value from base64 and save"));
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
        g_signal_connect(menuitem, "activate",
                        G_CALLBACK(on_b64decode_and_save_item_clicked),
                        (gpointer)node_value);
    }
    gtk_widget_show_all(menu);
    gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
                   event ? event->button : 0,
                   gdk_event_get_time((GdkEvent*)event));
}

static void view_popup_menu(GtkWidget* widget, GdkEventButton* event,
                            gpointer userdata) {
    GtkTreeSelection* sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
    GtkTreeModel* model = NULL;
    GtkTreeIter iter;
    if(gtk_tree_selection_get_selected(sel, &model, &iter)) {
        char* value = NULL;
        gtk_tree_model_get(model, &iter, COLUMN_VALUE, &value, -1);
        if(value)
            do_view_popup_menu(value, event);
    }
}

static gboolean on_view_popup_menu(GtkWidget* widget, gpointer data) {
    view_popup_menu(widget, NULL, data);
    return TRUE;
}

static gboolean on_button_pressed(GtkWidget* widget, GdkEventButton* event,
                                  gpointer userdata) {
    GTK_WIDGET_CLASS(GTK_TREE_VIEW_GET_CLASS(widget))
        ->button_press_event(widget, event);
    if(event->type == GDK_BUTTON_PRESS && event->button == 3)
        view_popup_menu(widget, event, userdata);
    return TRUE;
}

static void json_viewer_init(JsonViewer* self) {
    self->store = gtk_tree_store_new(COLUMNS_COUNT,
                                     G_TYPE_STRING, G_TYPE_STRING);
    gtk_tree_view_set_model(GTK_TREE_VIEW(self), GTK_TREE_MODEL(self->store));

    GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(self),
        -1, _("parameter"), renderer, "text", COLUMN_NAME, NULL);
    gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(self),
        -1, _("value"), renderer, "text", COLUMN_VALUE, NULL);
    g_signal_connect(self, "popup-menu", G_CALLBACK(on_view_popup_menu), self);
    g_signal_connect(self, "button-press-event",
                     G_CALLBACK(on_button_pressed), self);
}

gboolean json_viewer_load_from_data(JsonViewer* self, const gchar* data,
                                    gssize length) {
    gtk_tree_store_clear(self->store);
    GtkTreeIter iter;
    gchar buf[64];

    if(length == 0)
        return TRUE;

    JsonParser* parser = json_parser_new();
    if(!json_parser_load_from_data(parser, data, length, NULL)) {
        g_object_unref(parser);
        return FALSE;
    }

    typedef struct {
        guint elemid;
        gboolean hasparent;
        GtkTreeIter parent;
    } Task;

    JsonReader* reader = json_reader_new(json_parser_get_root(parser));
    GArray* queue = g_array_new(FALSE, FALSE, sizeof(Task));

    {
        Task task = {0, FALSE};
        g_array_append_val(queue, task);
    }

    gboolean is_first_arr_elem = FALSE;

    while(queue->len != 0) {
        Task* task = &g_array_index(queue, Task, queue->len - 1);
        for(;; ++task->elemid) {
            if(!json_reader_read_element(reader, task->elemid)) {
                json_reader_end_element(reader);
                g_array_remove_index_fast(queue, queue->len - 1);
                break;
            }
            gtk_tree_store_append(self->store, &iter,
                                  task->hasparent ? &task->parent : NULL);
            const gchar* name = json_reader_get_member_name(reader);
            if(!name || is_first_arr_elem) {
                sprintf(buf, "%d", task->elemid);
                name = buf;
            }

            gtk_tree_store_set(self->store, &iter, COLUMN_NAME, name, -1);

            is_first_arr_elem = FALSE;
            gboolean in_array = json_reader_is_array(reader);
            if(in_array || json_reader_is_object(reader)) {
                ++task->elemid;
                Task tmp = {0, TRUE, iter};
                g_array_append_val(queue, tmp);
                is_first_arr_elem = in_array;
                break;
            }
            else {
                JsonNode* node = json_reader_get_value(reader);
                const gchar* value = "{null}";
                if(node) {
                    GValue val = G_VALUE_INIT, strval = G_VALUE_INIT;
                    json_node_get_value(node, &val);
                    if(G_VALUE_HOLDS_STRING(&val))
                        value = g_value_get_string(&val);
                    else {
                        g_value_init(&strval, G_TYPE_STRING);
                        if(!g_value_type_transformable(G_VALUE_TYPE(&val),
                                                       G_TYPE_STRING)) {
                            value = "{unknown type}";
                            g_warning("cannot convert gvalue to string");
                        } else {
                            g_value_transform(&val, &strval);
                            value = g_value_get_string(&strval);
                        }
                    }
                }
                gtk_tree_store_set(self->store, &iter, COLUMN_VALUE, value, -1);
                json_reader_end_element(reader);
            }
        }
    }

    g_array_unref(queue);
    g_object_unref(parser);
    return TRUE;
}

void json_viewer_clear(JsonViewer* self) {
    gtk_tree_store_clear(self->store);
}

GtkWidget* json_viewer_new() {
    return g_object_new(JSON_VIEWER_TYPE, NULL);
}

#endif /* HAVE_JSON_GLIB */
